%{
#include <stdio.h>
#include "list.h"

int yylex ();
%}

%token INTERFACE METHOD RETURNS
%token ACC_OPEN ACC_CLOSE PAR_OPEN PAR_CLOSE COMMA
%token <str> STRING

%union {
    char* str;
}


%%

def : ACC_OPEN interfaces ACC_CLOSE;
interfaces : interface
           | interface interfaces;
interface : interface_name ACC_OPEN methods ACC_CLOSE;
interface_name : INTERFACE STRING { add_interface ($2); };
methods : method
        | method methods;
method : method_name PAR_OPEN args PAR_CLOSE RETURNS PAR_OPEN ret PAR_CLOSE;
method_name : METHOD STRING { add_method ($2); };
args : arg
     | arg COMMA args;
arg : STRING STRING { add_arg ($1, $2); };
    | ;
ret : STRING { set_return ($1); };

%%

char* dir_out;
char* package;

struct interface* cur_interface;
struct method* cur_method;

int yyerror (const char* c) { fprintf (stderr, "error %s\n", c); }

struct interface {
  struct list_head lh;
  char* name;
  struct list_head methods;
};

struct method {
  struct list_head lh;
  char* name;
  struct list_head args;
  char* ret_type;
};

struct arg {
  struct list_head lh;
  char* type;
  char* name;
};

LIST_HEAD(interfaces);

void add_interface (char* name) {
  struct interface* new = malloc (sizeof (struct interface));
  new->name = name;
  new->methods.prev = &new->methods;
  new->methods.next = &new->methods;

  cur_interface = new;
  list_add (&new->lh, &interfaces);
}

void add_method (char* name) {
  struct method* new = malloc (sizeof (struct method));
  new->name = name;
  new->args.prev = &new->args;
  new->args.next = &new->args;

  cur_method = new;
  
  list_add (&new->lh, &cur_interface->methods);
}

void add_arg (char* type, char *name) {
  struct arg* new = malloc (sizeof (struct arg));
  new->name = name;
  new->type = type;
  new->lh.prev = &new->lh;
  new->lh.next = &new->lh;

  list_add (&new->lh, &cur_method->args);
}

void set_return (char* type) {
  cur_method->ret_type = type;
}

void write_local (struct interface* interface) {
  struct method* method;
  struct arg* arg;
  int i, j, k;
  char filename[255] = {0};
  sprintf (filename, "%s/Local%s.java", dir_out, interface->name);
  remove (filename);
  FILE* file = fopen (filename, "a");

  fprintf (file, "package %s;\n\n", package);
  fprintf (file, "import cs543.library.rmi.RmiServerInterface;\nimport cs543.library.rmi.RmiServerRegistry;\nimport org.json.JSONArray;\n\n");
  fprintf (file, "public class Local%s extends RmiServerInterface {\n",
      interface->name);
  fprintf (file, "@Override\npublic void register () {\n");
  fprintf (file, "new RmiServerRegistry ().register (\"%s\", this);\n}\n", interface->name);
  fprintf (file, "@Override\npublic JSONArray call (String method, JSONArray args) {\n");
  fprintf (file, "%s obj = new %s ();\n", interface->name, interface->name);

  list_for_each_entry(method, &interface->methods, lh) {
    fprintf (file, "if (method.equals (\"%s\")) {\ntry {\n", method->name);
    
    i = 0;
    list_for_each_entry(arg, &method->args, lh) {
      if (strcmp (method->ret_type, "String") == 0)
        fprintf (file, "String r%d = args.getString(%d);\n", i, i);
      else if (strcmp (method->ret_type, "int") == 0)
        fprintf (file, "int r%d = args.getInt(%d);\n", i, i);
      else if (strcmp (method->ret_type, "boolean") == 0)
        fprintf (file, "boolean r%d = args.getBoolean(%d);\n", i,i);

      i++;
    }

    fprintf (file, "%s ret = obj.%s (", method->ret_type, method->name);

    k = 0;
    for (j=0; j<i; j++) {
      if (k == 0) k = 1;
      else        fprintf (file, ", ");
      fprintf (file, "r%d", j);
    }

    fprintf (file, ");\nJSONArray arr = new JSONArray ();\narr.put (ret);\nreturn arr;\n");
    fprintf (file, "} catch (Exception e) {}\n");
    fprintf (file, "}\n");
  }
  
  fprintf (file, "return null;\n}\n}");
  fclose (file);
}

void write_server_main (struct list_head* interfaces) {
  struct interface* interface;
  char filename[255] = {0};
  sprintf (filename, "%s/RmiServerInstance.java", dir_out);
  remove (filename);
  FILE* file = fopen (filename, "a");

  fprintf (file, "package %s;\n\n", package);
  fprintf (file, "import cs543.library.rmi.RmiServer;");
  fprintf (file, "public class RmiServerInstance {\n");
  fprintf (file, "public RmiServerInstance () {\n");

  fprintf (file, "try {\nThread th = new Thread (new RmiServer ());\nth.start ();\n");
  
  list_for_each_entry(interface, interfaces, lh) {
    fprintf (file, "new Local%s ().register ();\n", interface->name);
  }

  fprintf (file, "} catch (Exception e){}\n}\n}");
}

void write_remote (struct interface* interface) {
  struct method* method;
  struct arg* arg;
  int i;
  char filename[255] = {0};
  sprintf (filename, "%s/Remote%s.java", dir_out, interface->name);
  remove (filename);
  FILE* file = fopen (filename, "a");

  fprintf (file, "package %s;\n\n", package);
  fprintf (file, "import cs543.library.rmi.RmiClientInterface;\nimport org.json.JSONArray;\n\n");
  fprintf (file, "public class Remote%s extends RmiClientInterface {\n",
      interface->name);
  fprintf (file, "public Remote%s (String name) {\nsuper (name);\n}\n\n",
      interface->name);
  
  list_for_each_entry(method, &interface->methods, lh) {
    fprintf (file, "public %s %s (", method->ret_type, method->name);

    i = 0;
    list_for_each_entry(arg, &method->args, lh) {
      if (i == 0) i = 1;
      else        fprintf (file, ", ");
      fprintf (file, "%s %s", arg->type, arg->name);
    }

    fprintf (file, ") {\nJSONArray args = new JSONArray ();\n");

    if (strcmp (method->ret_type, "String") == 0)
      fprintf (file, "String ret = \"\";\n");
    else if (strcmp (method->ret_type, "int") == 0)
      fprintf (file, "int ret = 0;\n");
    else if (strcmp (method->ret_type, "boolean") == 0)
      fprintf (file, "boolean ret = false;\n");

    list_for_each_entry(arg, &method->args, lh) {
      fprintf (file, "args.put (%s);\n", arg->name);
    }

    fprintf (file, "JSONArray rep = invoque (\"%s\", \"%s\", args);\ntry{\n", interface->name, method->name);
    if (strcmp (method->ret_type, "String") == 0)
      fprintf (file, "ret = rep.getString (0);\n");
    else if (strcmp (method->ret_type, "int") == 0)
      fprintf (file, "ret = rep.getInt (0);\n");
    else if (strcmp (method->ret_type, "boolean") == 0)
      fprintf (file, "ret = rep.getBoolean (0);\n");

    fprintf (file, "} catch (Exception e) {}\nreturn ret;\n}\n");
  }
  
  fprintf (file, "}");
  fclose (file);
}

int main (int argc, char** argv) {
  struct interface* interface;
  struct method* method;

  dir_out = argv[1];
  package = argv[2];
  
  yyparse ();

  write_server_main (&interfaces);
  list_for_each_entry(interface, &interfaces, lh) {
    write_local (interface);
    write_remote (interface);
  }

  return 0;
}

